HW settings:
NOTE: some sweeps sometimes sounded cropped or incomplete. Measures of 0 deg, 100 to 260 deg have been taken with the same setup but on 2/4/2025

Out signal used during the recordings: 1-40Khz 5ms sweep.
import sounddevice as sd
import numpy as np
import scipy.signal as signal
import time
from matplotlib import pyplot as plt
def get_soundcard_outstream(device_list):
for i, each in enumerate(device_list):
dev_name = each['name']
asio_in_name = 'MCHStreamer' in dev_name
if asio_in_name:
return i
def pow_two_pad_and_window(vec, show):
window = signal.windows.tukey(len(vec), alpha=0.2)
windowed_vec = vec * window
padded_windowed_vec = np.pad(windowed_vec, (0, 2**int(np.ceil(np.log2(len(windowed_vec)))) - len(windowed_vec)))
if show:
dur = len(padded_windowed_vec) / fs
t = np.linspace(0, dur, len(windowed_vec))
plt.figure()
plt.subplot(2, 1, 1)
plt.plot(t, windowed_vec)
plt.subplot(2, 1, 2)
plt.specgram(windowed_vec, NFFT=64, noverlap=32, Fs=fs)
plt.show()
return padded_windowed_vec/max(padded_windowed_vec)
def pow_two(vec):
return np.pad(vec, (0, 2**int(np.ceil(np.log2(len(vec)))) - len(vec)))
if __name__ == "__main__":
fs = 96e3
dur = 5e-3
hi_freq = 1e3
low_freq = 40e3
n_sweeps = 5
t_tone = np.linspace(0, dur, int(fs*dur))
chirp = signal.chirp(t_tone, hi_freq, t_tone[-1], low_freq)
sig = pow_two_pad_and_window(chirp, show=True)
silence_dur = 100 # [ms]
silence_samples = int(silence_dur * fs/1000)
silence_vec = np.zeros((silence_samples, ))
full_sig = pow_two(np.concatenate((sig, silence_vec)))
#print('len = ', len(full_sig))
stereo_sig = np.hstack([full_sig.reshape(-1, 1), full_sig.reshape(-1, 1)])
output_sig = np.float32(stereo_sig)
current_frame = 0
def callback(outdata, frames, time, status):
global current_frame
if status:
print(status)
chunksize = min(len(output_sig) - current_frame, frames)
outdata[:chunksize] = output_sig[current_frame:current_frame + chunksize]
if chunksize < frames:
outdata[chunksize:] = 0
raise sd.CallbackAbort()
current_frame += chunksize
device = get_soundcard_outstream(sd.query_devices())
try:
for i in range(n_sweeps):
stream = sd.OutputStream(samplerate=fs,
blocksize=0,
device=device,
channels=2,
callback=callback,
latency='low')
with stream:
while stream.active:
pass
current_frame = 0
print('Chirped %d' % (i+1))
time.sleep(1)
except KeyboardInterrupt:
print('Interrupted by user')
Chirped 1 Chirped 2 Chirped 3 Chirped 4 Chirped 5
import os
import soundfile as sf
import numpy as np
import matplotlib.pyplot as plt
from scipy import fft
# Load audio files, then plot a 6x6 grid
DIR = "./array_calibration/226_238/2025-03-27/original/" # Directory containing the audio files
audio_files = os.listdir(DIR) # List all files in the sweeps directory
audio_files.sort() # Sort the files in ascending order
# Directory to save the extracted channels
output_dir = "./array_calibration/226_238/2025-03-27/extracted_channels/"
os.makedirs(output_dir, exist_ok=True) # Create the directory if it doesn't exist
# Path to the multi-channel WAV file
for file in audio_files:
file_path = os.path.join(DIR, file)
angle_name = file.split('.')[0]
print(f"Processing file: {angle_name}")
# Read the multi-channel WAV file
audio_data, sample_rate = sf.read(DIR + file)
# Check the shape of the audio data
print(f"Audio data shape: {audio_data.shape}") # (samples, channels)
# Extract individual channels
num_channels = audio_data.shape[1] # Number of channels
channels = [audio_data[:, i] for i in range(num_channels)]
# Save each channel as a separate WAV file
for i, channel_data in enumerate(channels):
output_file = os.path.join(output_dir, angle_name+f"_{i + 1}.wav") # Path to the output file
sf.write(output_file, channel_data, sample_rate)
print(f"Saved channel {i + 1} to {output_file}")
Processing file: 000 Audio data shape: (894348, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/000_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/000_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/000_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/000_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/000_5.wav Processing file: 010 Audio data shape: (938608, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/010_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/010_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/010_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/010_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/010_5.wav Processing file: 020 Audio data shape: (1032987, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/020_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/020_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/020_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/020_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/020_5.wav Processing file: 030 Audio data shape: (916910, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/030_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/030_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/030_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/030_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/030_5.wav Processing file: 040 Audio data shape: (878504, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/040_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/040_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/040_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/040_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/040_5.wav Processing file: 050 Audio data shape: (925263, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/050_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/050_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/050_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/050_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/050_5.wav Processing file: 060 Audio data shape: (894346, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/060_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/060_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/060_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/060_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/060_5.wav Processing file: 070 Audio data shape: (1037979, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/070_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/070_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/070_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/070_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/070_5.wav Processing file: 080 Audio data shape: (930255, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/080_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/080_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/080_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/080_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/080_5.wav Processing file: 090 Audio data shape: (1032987, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/090_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/090_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/090_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/090_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/090_5.wav Processing file: 100 Audio data shape: (1156554, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/100_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/100_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/100_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/100_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/100_5.wav Processing file: 110 Audio data shape: (977012, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/110_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/110_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/110_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/110_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/110_5.wav Processing file: 120 Audio data shape: (1048830, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/120_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/120_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/120_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/120_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/120_5.wav Processing file: 130 Audio data shape: (897708, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/130_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/130_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/130_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/130_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/130_5.wav Processing file: 140 Audio data shape: (867656, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/140_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/140_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/140_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/140_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/140_5.wav Processing file: 150 Audio data shape: (960307, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/150_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/150_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/150_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/150_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/150_5.wav Processing file: 160 Audio data shape: (1020410, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/160_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/160_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/160_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/160_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/160_5.wav Processing file: 170 Audio data shape: (1020410, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/170_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/170_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/170_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/170_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/170_5.wav Processing file: 180 Audio data shape: (776637, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/180_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/180_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/180_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/180_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/180_5.wav Processing file: 190 Audio data shape: (1117285, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/190_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/190_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/190_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/190_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/190_5.wav Processing file: 200 Audio data shape: (1258422, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/200_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/200_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/200_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/200_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/200_5.wav Processing file: 210 Audio data shape: (1048829, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/210_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/210_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/210_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/210_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/210_5.wav Processing file: 220 Audio data shape: (834244, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/220_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/220_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/220_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/220_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/220_5.wav Processing file: 230 Audio data shape: (1039612, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/230_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/230_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/230_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/230_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/230_5.wav Processing file: 240 Audio data shape: (1006175, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/240_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/240_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/240_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/240_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/240_5.wav Processing file: 250 Audio data shape: (935247, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/250_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/250_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/250_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/250_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/250_5.wav Processing file: 260 Audio data shape: (853446, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/260_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/260_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/260_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/260_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/260_5.wav Processing file: 270 Audio data shape: (877641, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/270_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/270_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/270_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/270_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/270_5.wav Processing file: 280 Audio data shape: (911053, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/280_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/280_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/280_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/280_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/280_5.wav Processing file: 290 Audio data shape: (952817, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/290_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/290_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/290_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/290_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/290_5.wav Processing file: 300 Audio data shape: (997825, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/300_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/300_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/300_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/300_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/300_5.wav Processing file: 310 Audio data shape: (936112, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/310_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/310_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/310_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/310_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/310_5.wav Processing file: 320 Audio data shape: (938608, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/320_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/320_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/320_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/320_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/320_5.wav Processing file: 330 Audio data shape: (986229, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/330_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/330_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/330_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/330_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/330_5.wav Processing file: 340 Audio data shape: (913549, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/340_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/340_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/340_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/340_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/340_5.wav Processing file: 350 Audio data shape: (926895, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/350_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/350_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/350_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/350_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/350_5.wav Processing file: 360 Audio data shape: (894348, 5) Saved channel 1 to ./array_calibration/226_238/2025-03-27/extracted_channels/360_1.wav Saved channel 2 to ./array_calibration/226_238/2025-03-27/extracted_channels/360_2.wav Saved channel 3 to ./array_calibration/226_238/2025-03-27/extracted_channels/360_3.wav Saved channel 4 to ./array_calibration/226_238/2025-03-27/extracted_channels/360_4.wav Saved channel 5 to ./array_calibration/226_238/2025-03-27/extracted_channels/360_5.wav
# List all extracted channel files separated by channel number
from natsort import natsorted
import os
# Directory containing the extracted channels
extracted_channels_dir = "./array_calibration/226_238/2025-03-27/extracted_channels/"
# List all extracted channel files
channel_files = os.listdir(extracted_channels_dir)
# Filter out directories, keep only files
channel_files = [f for f in channel_files if os.path.isfile(os.path.join(extracted_channels_dir, f))]
# Sort the files naturally by the last part of their names (e.g., channel number)
sorted_channel_files = natsorted(channel_files, key=lambda x: int(x.split('_')[-1].split('.')[0]))
# Group files by the last part of their name (channel number)
grouped_files = {}
for file in sorted_channel_files:
# Extract the channel number from the file name (e.g., "350_1.wav" -> "1")
channel_number = int(file.split('_')[-1].split('.')[0])
# Group files by channel number
if channel_number not in grouped_files:
grouped_files[channel_number] = []
grouped_files[channel_number].append(file)
for i in range(len(grouped_files)):
grouped_files[i+1].sort()
# Print grouped files
for channel_number, files in grouped_files.items():
print(f"Channel {channel_number}:")
for f in files:
print(f" {f}")
Channel 1: 000_1.wav 010_1.wav 020_1.wav 030_1.wav 040_1.wav 050_1.wav 060_1.wav 070_1.wav 080_1.wav 090_1.wav 100_1.wav 110_1.wav 120_1.wav 130_1.wav 140_1.wav 150_1.wav 160_1.wav 170_1.wav 180_1.wav 190_1.wav 200_1.wav 210_1.wav 220_1.wav 230_1.wav 240_1.wav 250_1.wav 260_1.wav 270_1.wav 280_1.wav 290_1.wav 300_1.wav 310_1.wav 320_1.wav 330_1.wav 340_1.wav 350_1.wav 360_1.wav Channel 2: 000_2.wav 010_2.wav 020_2.wav 030_2.wav 040_2.wav 050_2.wav 060_2.wav 070_2.wav 080_2.wav 090_2.wav 100_2.wav 110_2.wav 120_2.wav 130_2.wav 140_2.wav 150_2.wav 160_2.wav 170_2.wav 180_2.wav 190_2.wav 200_2.wav 210_2.wav 220_2.wav 230_2.wav 240_2.wav 250_2.wav 260_2.wav 270_2.wav 280_2.wav 290_2.wav 300_2.wav 310_2.wav 320_2.wav 330_2.wav 340_2.wav 350_2.wav 360_2.wav Channel 3: 000_3.wav 010_3.wav 020_3.wav 030_3.wav 040_3.wav 050_3.wav 060_3.wav 070_3.wav 080_3.wav 090_3.wav 100_3.wav 110_3.wav 120_3.wav 130_3.wav 140_3.wav 150_3.wav 160_3.wav 170_3.wav 180_3.wav 190_3.wav 200_3.wav 210_3.wav 220_3.wav 230_3.wav 240_3.wav 250_3.wav 260_3.wav 270_3.wav 280_3.wav 290_3.wav 300_3.wav 310_3.wav 320_3.wav 330_3.wav 340_3.wav 350_3.wav 360_3.wav Channel 4: 000_4.wav 010_4.wav 020_4.wav 030_4.wav 040_4.wav 050_4.wav 060_4.wav 070_4.wav 080_4.wav 090_4.wav 100_4.wav 110_4.wav 120_4.wav 130_4.wav 140_4.wav 150_4.wav 160_4.wav 170_4.wav 180_4.wav 190_4.wav 200_4.wav 210_4.wav 220_4.wav 230_4.wav 240_4.wav 250_4.wav 260_4.wav 270_4.wav 280_4.wav 290_4.wav 300_4.wav 310_4.wav 320_4.wav 330_4.wav 340_4.wav 350_4.wav 360_4.wav Channel 5: 000_5.wav 010_5.wav 020_5.wav 030_5.wav 040_5.wav 050_5.wav 060_5.wav 070_5.wav 080_5.wav 090_5.wav 100_5.wav 110_5.wav 120_5.wav 130_5.wav 140_5.wav 150_5.wav 160_5.wav 170_5.wav 180_5.wav 190_5.wav 200_5.wav 210_5.wav 220_5.wav 230_5.wav 240_5.wav 250_5.wav 260_5.wav 270_5.wav 280_5.wav 290_5.wav 300_5.wav 310_5.wav 320_5.wav 330_5.wav 340_5.wav 350_5.wav 360_5.wav
# Define the matched filter function
def matched_filter(recording, chirp_template):
chirp_template = chirp_template[::-1] # Time-reversed chirp
filtered_output = signal.fftconvolve(recording, chirp_template, mode='valid')
return filtered_output
# Detect peaks in the matched filter output
def detect_peaks(filtered_output, threshold=0.5):
peaks, _ = signal.find_peaks(filtered_output, height=threshold * np.max(filtered_output))
return peaks
# Process each channel
DIR_first_sweep = "./array_calibration/226_238/2025-03-27/extracted_channels/first_sweep/" # Directory to save the first sweeps
channel_number = 1
for i in range(len(grouped_files)):
files = grouped_files[i+1]
print(f"Processing Channel {channel_number}:")
# Create a new figure for each channel
fig, ax = plt.subplots(figsize=(15, 5))
ax.set_title(f"Channel {channel_number}")
ax.set_xlabel("Seconds")
ax.set_ylabel("Amplitude")
ax.grid(True)
for file in files[0:(len(files)-1)]:
file_path = os.path.join(extracted_channels_dir, file)
recording, sample_rate = sf.read(file_path)
# Apply matched filtering
filtered_output = matched_filter(recording, chirp)
# Detect peaks
peaks = detect_peaks(filtered_output)
if len(peaks) > 0:
# Extract the first sweep
first_sweep_start = peaks[0]
first_sweep_end = first_sweep_start + len(chirp)
first_sweep = recording[first_sweep_start:first_sweep_end]
# Save the first sweep
sf.write(DIR_first_sweep + file, first_sweep, int(fs))
# Plot the first sweep
angle_name = file.split('_')[0]
if int(angle_name):
ax.plot(np.linspace(0,len(first_sweep),len(first_sweep))/fs, first_sweep, label=f"{angle_name}")
else:
print(f"No sweeps detected in {file} - Channel {channel_number}")
# Plot all angles, skipping '360' (= to zero degrees)
fig1, axs = plt.subplots(9, 4, figsize=(15, 30), sharey=True)
angles = [file.split('_')[0] for file in files] # Extract angle names from filenames
idx_to_plot = 0
for idx, file in enumerate(files):
if angles[idx] == '360':
continue # Skip the 360 angle
file_path = os.path.join(DIR_first_sweep, file)
audio, fs = sf.read(file_path)
rms = np.sqrt(np.mean(audio**2))
rms_db = 20 * np.log10(rms)
row = idx_to_plot // 4
col = idx_to_plot % 4
ax = axs[row, col]
ax.plot(np.linspace(0, len(audio) / fs, len(audio)), audio)
ax.set_title(f"Angle: {angles[idx]} degrees ") # Use extracted angle name with units
ax.set_xlabel("Time (s)")
ax.set_ylabel("Amplitude")
ax.grid(True)
ax.legend([f'RMS: {rms:.5f}\nRMS: {rms_db:.5f} dB'], loc='upper left')
idx_to_plot += 1
plt.suptitle(f"Channel {channel_number}: First Sweep for Each Angle", fontsize=20)
plt.tight_layout(rect=[0, 0.03, 1, 0.95]) # Adjust layout to make room for suptitle
plt.show(block = False)
ax.legend()
channel_number += 1
plt.show(block = False)
Processing Channel 1:
No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
Processing Channel 2:
No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
Processing Channel 3:
No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
Processing Channel 4:
No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
Processing Channel 5:
No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
# RMS values of the first sweep for each channel
num_channels = len(grouped_files)
fig_polar, axs_polar = plt.subplots(1, num_channels, figsize=(18, 5), subplot_kw={'projection': 'polar'})
fig_polar.suptitle("RMS Values of First Sweeps for Each Channel", fontsize=16)
for i in range(num_channels):
channel_number = i + 1
files = grouped_files[channel_number]
rms_values = []
rms_values_norm_db = []
angles = []
for file in files:
file_path = os.path.join(DIR_first_sweep, file)
audio, fs = sf.read(file_path)
rms = np.sqrt(np.mean(audio**2))
rms_values.append(rms)
rms_values_norm = rms_values / rms_values[0]
rms_values_norm_db = 20 * np.log10(rms_values_norm)
angle_name = file.split('_')[0]
angles.append(int(angle_name))
# Convert angles to radians
angles_rad = np.radians(angles)
# Plot RMS values in polar plot
ax_polar = axs_polar[i] if num_channels > 1 else axs_polar
ax_polar.plot(angles_rad, rms_values_norm_db, linestyle='-', label=f"Channel {channel_number}")
ax_polar.set_title(f"Channel {channel_number}")
ax_polar.set_theta_zero_location("N") # Set 0 degrees to North
ax_polar.set_theta_direction(-1) # Set clockwise direction
ax_polar.set_xticks(np.linspace(0, 2 * np.pi, 18, endpoint=False)) # Set angle ticks
ax_polar.set_xlabel("Angle (degrees)")
ax_polar.set_ylabel("RMS Value dB", position=(0, 0.85), ha='left')
ax_polar.set_yticks(np.linspace(-8, 2, 6))
ax_polar.set_rlabel_position(0)
# Linear plot of all channels
fig_linear, ax_linear = plt.subplots(figsize=(10, 6))
fig_linear.suptitle("RMS Values of Overall Recording for All Channels", fontsize=16)
for i in range(num_channels):
channel_number = i + 1
files = grouped_files[channel_number]
rms_values = []
angles = []
for file in files:
file_path = os.path.join(DIR_first_sweep, file)
audio, fs = sf.read(file_path)
rms = np.sqrt(np.mean(audio**2))
rms_values.append(rms)
rms_values_norm = rms_values / rms_values[0]
rms_values_norm_db = 20 * np.log10(rms_values_norm)
angle_name = file.split('_')[0]
angles.append(int(angle_name))
# Plot RMS values in linear plot
ax_linear.plot(angles, rms_values_norm_db, marker='.', linestyle='-', label=f"Channel {channel_number}")
ax_linear.set_xlabel("Angle (degrees)")
ax_linear.set_xticks(np.linspace(0, 380, 19, endpoint=False)) # Set angle ticks
ax_linear.set_ylabel("RMS Value dB")
ax_linear.legend()
ax_linear.grid(True)
fig_polar.tight_layout()
plt.show(block = False)
# RMS values of the overall recording for each channel and each angle
num_channels = len(grouped_files)
fig_polar, axs_polar = plt.subplots(1, num_channels, figsize=(18, 5), subplot_kw={'projection': 'polar'})
fig_polar.suptitle("RMS Values of Overall Recording for Each Channel", fontsize=16)
for i in range(num_channels):
channel_number = i + 1
files = grouped_files[channel_number]
rms_values = []
rms_values_norm_db = []
angles = []
for file in files:
file_path = os.path.join(extracted_channels_dir, file)
audio, fs = sf.read(file_path)
rms = np.sqrt(np.mean(audio**2))
rms_values.append(rms)
rms_values_norm = rms_values / rms_values[0]
rms_values_norm_db = 20 * np.log10(rms_values_norm)
angle_name = file.split('_')[0]
angles.append(int(angle_name))
# Convert angles to radians
angles_rad = np.radians(angles)
# Plot RMS values in polar plot
ax_polar = axs_polar[i] if num_channels > 1 else axs_polar
ax_polar.plot(angles_rad, rms_values_norm_db, linestyle='-', label=f"Channel {channel_number}")
ax_polar.set_title(f"Channel {channel_number}")
ax_polar.set_theta_zero_location("N") # Set 0 degrees to North
ax_polar.set_theta_direction(-1) # Set clockwise direction
ax_polar.set_xticks(np.linspace(0, 2 * np.pi, 18, endpoint=False)) # Set angle ticks
ax_polar.set_xlabel("Angle (degrees)")
ax_polar.set_ylabel("RMS Value dB", position=(0, 1), ha='left')
ax_polar.set_rlabel_position(0)
# Linear plot of all channels
fig_linear, ax_linear = plt.subplots(figsize=(10, 6))
fig_linear.suptitle("RMS Values of Overall Recording for All Channels", fontsize=16)
for i in range(num_channels):
channel_number = i + 1
files = grouped_files[channel_number]
rms_values = []
angles = []
for file in files:
file_path = os.path.join(extracted_channels_dir, file)
audio, fs = sf.read(file_path)
rms = np.sqrt(np.mean(audio**2))
rms_values.append(rms)
angle_name = file.split('_')[0]
angles.append(int(angle_name))
# Plot RMS values in linear plot
ax_linear.plot(angles, rms_values, marker='.', linestyle='-', label=f"Channel {channel_number}")
ax_linear.set_xlabel("Angle (degrees)")
ax_linear.set_xticks(np.linspace(0, 380, 19, endpoint=False)) # Set angle ticks
ax_linear.set_ylabel("RMS Value")
ax_linear.legend()
ax_linear.grid(True)
fig_polar.tight_layout()
plt.show(block = False)
# Central frequencies of the bands
central_freq = np.array([4e3, 6e3, 8e3, 10e3, 12e3, 14e3, 16e3, 18e3, 20e3, 22e3, 24e3, 26e3, 28e3, 30e3, 32e3, 34e3, 36e3, 38e3])
BW = 1e3 # Bandwidth of the bands
linestyles = ["-", "--", "-.", ":", "-", "--"] # Line styles for the plot
# Group central frequencies into 3 sets of 6 bands each
num_bands_per_plot = 6
central_freq_sets = [central_freq[i * num_bands_per_plot:(i + 1) * num_bands_per_plot] for i in range(3)]
# Number of microphones
num_mics = num_channels
# Plot for each microphone
for mic in range(1, num_mics + 1):
files = grouped_files[mic]
angles = [int(file.split('_')[0]) for file in files] # Extract angles from filenames
# Create a figure with 3 polar subplots
fig, axes = plt.subplots(1, 3, subplot_kw={"projection": "polar"}, figsize=(15, 5))
plt.suptitle(f"Polar Frequency Response - Microphone {mic}", fontsize=20)
for ax_idx, ax in enumerate(axes):
ii = 0
for fc in central_freq_sets[ax_idx]:
audio_patt = []
for file in files:
file_path = os.path.join(DIR_first_sweep, file)
audio, fs = sf.read(file_path)
# Compute FFT
audio_freq = fft.fft(audio, n=2048)
audio_freq = audio_freq[:1024]
freqs = fft.fftfreq(2048, 1 / fs)[:1024]
# Compute mean radiance in the band
band_mean = np.mean(np.abs(audio_freq[(freqs > fc - BW) & (freqs < fc + BW)]))
audio_patt.append(band_mean)
# Normalize and plot
audio_patt_norm = audio_patt / audio_patt[0] # Normalize the radiance
audio_patt_norm_dB = 20 * np.log10(audio_patt_norm) # Convert the radiance to dB
if fc >= 10e3:
label = f"{fc / 1e3:.0f} kHz"
else:
label = f"{fc / 1e3:.0f} kHz"
ax.plot(np.deg2rad(angles), audio_patt_norm_dB, label=label, linestyle=linestyles[ii])
ii +=1
# Configure polar plot
ax.legend(loc="upper right")
ax.set_theta_offset(np.pi / 2)
ax.set_theta_zero_location("N") # Set 0 degrees to North
ax.set_theta_direction(-1) # Set clockwise direction
ax.set_xticks(np.linspace(0, 2 * np.pi, 18, endpoint=False)) # Set angle ticks
ax.set_yticks(np.linspace(-35, 0, 6))
ax.set_xlabel("Angle (degrees)")
ax.set_ylabel("RMS Value dB", position=(0, 1), ha='left')
ax.set_rlabel_position(0)
plt.tight_layout()
plt.show()